﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Beaker;

namespace OpenCube.Nbt.Mapping
{
	/// <summary>
	/// Abstract base class for NBT type converters to implementing INbtTypeConverter interfaces with single types.
	/// </summary>
	public abstract class NbtSimpleTypeConverter
		: INbtTypeConverter
	{
		bool INbtTypeConverter.CanConvertToNbt(Type sourceModel)
		{
			return CanConvertToNbt(sourceModel);
		}

		protected abstract bool CanConvertToNbt(Type sourceModel);

		bool INbtTypeConverter.CanConvertToModel(IReadOnlyList<Type> sourceNbtTypes, Type targetModelType)
		{
			throw new NotImplementedException();
		}

		protected abstract bool CanConvertToModel(Type[] sourceNbtTypes, Type targetModelType);

		object INbtTypeConverter.ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue)
		{
			throw new NotImplementedException();
		}

		protected abstract object ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue);

		IEnumerable<object> INbtTypeConverter.ConvertToNbtValue(IConversionContext conversionContext, object modelValue)
		{
			throw new NotImplementedException();
		}

		protected abstract IEnumerable<object> ConvertToNbtValue(IConversionContext conversionContext, object modelValue);
	}

	/// <summary>
	/// Abstract base class for NBT type converters to implementing INbtTypeConverter interfaces with single types.
	/// </summary>
	public abstract class NbtSimpleTypeConverter<TModel>
		: INbtTypeConverter
	{
		bool INbtTypeConverter.CanConvertToNbt(Type sourceModel)
		{
			if (sourceModel != typeof(TModel))
				return false;

			return true;
		}

		bool INbtTypeConverter.CanConvertToModel(IReadOnlyList<Type> sourceNbtTypes, Type targetModelType)
		{
			if (targetModelType != typeof(TModel))
				return false;

			if (sourceNbtTypes.Count() != 1)
				return false;

			return CanConvert(sourceNbtTypes.Single());
		}

		protected abstract bool CanConvert(Type nbtValueType);

		object INbtTypeConverter.ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue)
		{
			return ConvertToModelValue(nbtValue.Single());
		}

		protected abstract object ConvertToModelValue(object nbtValue);

		IEnumerable<object> INbtTypeConverter.ConvertToNbtValue(IConversionContext conversionContext, object modelValue)
		{
			Maybe<object> result = ConvertToNbtValue(modelValue);
			if (result.HasValue)
				yield return result.Value;
		}

		protected abstract Maybe<object> ConvertToNbtValue(object modelValue);
	}

	/// <summary>
	/// Abstract base class for NBT type converters to implementing INbtTypeConverter interfaces with single types.
	/// </summary>
	public abstract class NbtSimpleTypeConverter<TModel, TNbt>
		: INbtTypeConverter
	{
		bool INbtTypeConverter.CanConvertToNbt(Type sourceModel)
		{
			if (sourceModel != typeof(TModel))
				return false;

			return true;
		}

		bool INbtTypeConverter.CanConvertToModel(IReadOnlyList<Type> sourceNbtTypes, Type targetModelType)
		{
			if (targetModelType != typeof(TModel))
				return false;

			if (sourceNbtTypes.Count != 1)
				return false;

			return sourceNbtTypes.Single() == typeof(TNbt);
		}

		object INbtTypeConverter.ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue)
		{
			// no value input, then output the default value
			if (nbtValue[0] == null )
			{
				// try to get a preconfigured default value from the attribute
				if (conversionContext != null && conversionContext.MemberInfo != null)
				{
					var defaultValueAttribute = conversionContext.MemberInfo.GetCustomAttribute<DefaultValueAttribute>();
					return defaultValueAttribute.Value;
				}

				// non default, then default default.
				return default(TNbt);
			}

			return ConvertToModelValue((TNbt)nbtValue.Single());
		}

		protected abstract TModel ConvertToModelValue(TNbt nbtValue);

		IEnumerable<object> INbtTypeConverter.ConvertToNbtValue(IConversionContext conversionContext, object modelValue)
		{
			Maybe<TNbt> result = ConvertToNbtValue((TModel)modelValue);
			if (result.HasValue)
				yield return result.Value;
		}

		protected abstract Maybe<TNbt> ConvertToNbtValue(TModel modelValue);
	}

	/// <summary>
	/// Abstract base class for NBT type converters to implementing INbtTypeConverter interfaces with multiple types.
	/// </summary>
	public abstract class NbtMultiTypeConverter
		: INbtTypeConverter
	{
		bool INbtTypeConverter.CanConvertToNbt(Type sourceModel)
		{
			return CanConvertToNbt(sourceModel);
		}

		protected abstract bool CanConvertToNbt(Type sourceModel);

		bool INbtTypeConverter.CanConvertToModel(IReadOnlyList<Type> sourceNbtTypes, Type targetModelType)
		{
			return CanConvertToModel(sourceNbtTypes, targetModelType);
		}

		protected abstract bool CanConvertToModel(IReadOnlyList<Type> sourceNbtTypes, Type targetModelType);

		object INbtTypeConverter.ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue)
		{
			return ConvertToModelValue(conversionContext, nbtValue);
		}

		protected abstract object ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue);

		IEnumerable<object> INbtTypeConverter.ConvertToNbtValue(IConversionContext conversionContext, object modelValue)
		{
			return ConvertToNbtValue(conversionContext, modelValue);
		}

		protected abstract IEnumerable<object> ConvertToNbtValue(IConversionContext conversionContext, object modelValue);
	}

	/// <summary>
	/// Abstract base class for NBT type converters to implementing INbtTypeConverter interfaces with multiple types.
	/// </summary>
	public abstract class NbtMultiTypeConverter<TModel>
		: INbtTypeConverter
	{
		bool INbtTypeConverter.CanConvertToNbt(Type sourceModel)
		{
			return sourceModel == typeof(TModel);
		}

		bool INbtTypeConverter.CanConvertToModel(IReadOnlyList<Type> sourceNbtTypes, Type targetModelType)
		{
			return CanConvertToModel(sourceNbtTypes, targetModelType);
		}

		protected abstract bool CanConvertToModel(IReadOnlyList<Type> sourceNbtTypes, Type targetModelType);

		object INbtTypeConverter.ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue)
		{
			return ConvertToModelValue(conversionContext, nbtValue);
		}

		protected abstract TModel ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue);

		IEnumerable<object> INbtTypeConverter.ConvertToNbtValue(IConversionContext conversionContext, object modelValue)
		{
			return ConvertToNbtValue(conversionContext, (TModel)modelValue);
		}

		protected abstract IEnumerable<object> ConvertToNbtValue(IConversionContext conversionContext, TModel modelValue);
	}

}